import enum
import json
import random
import string

import board
import server_socket
from player import Player


class MSG_TYPE(enum.Enum):
	ERROR = -1
	UNKNOWN = 0
	START = 1
	QUIT = 2
	GIVE_CARD = 3
	CHANGE_NAME = 4

class GAME_STATE(enum.Enum):
	LOBBY = 0
	QUIT = 1
	PLAY = 2

class JSON_TYPE(str, enum.Enum): # TODO: use in jsons
	INFO  = 'info'
	QUERY = 'query'

	def __str__(self) -> str:
		return self.value

class Game:
	def __init__(self, serversocket, score_limit=66, nturn=10):
		self.players = {}
		self.board = None
		self.ssock = serversocket
		self.turn = 1
		self.score_limit = score_limit
		self.cards_dealt = nturn  # CARDS In Hand
		self.game_state = GAME_STATE.LOBBY

	@classmethod
	def get_controll_message_action(cls, msg):
		try:
			print('json:', msg)
			res = json.loads(msg)
			action = res.get('action')
			print('action:', action)
			if action == 'start':
				return MSG_TYPE.START
			elif action == 'set_name':
				return MSG_TYPE.CHANGE_NAME
			elif action == 'quit':
				return MSG_TYPE.QUIT
			return MSG_TYPE.UNKNOWN
		except(json.decoder.JSONDecodeError):
			print('json decode error...', msg)
			return MSG_TYPE.ERROR

	def run(self):
		print('game: running')
		print('waiting to start...')

		while self.game_state != GAME_STATE.QUIT:
			if self.game_state == GAME_STATE.LOBBY:
				self.lobby()
			else:
				self.game()
				max_score=max([p.score for p in self.players.values()])
				if max_score<self.score_limit:
					self.game_state=GAME_STATE.PLAY


	def process_lobby_message(self, client, msg):
		action = self.get_controll_message_action(msg)
		if action == MSG_TYPE.START:
			self.game_state = GAME_STATE.PLAY
		elif action == MSG_TYPE.CHANGE_NAME:
			self.handle_name_change(client, msg)
		elif action == MSG_TYPE.QUIT:
			self.game_state = GAME_STATE.QUIT
		#elif action == MSG_TYPE.CHAT:
			#broadcast chat...

	def handle_name_change(self, client, msg):
		res = json.loads(msg)
		name = res.get('name')
		print('renaming', self.players[client].name, 'to', name)
		self.players[client].name = name

	def lobby(self):
		for client, msg, op in self.ssock.listen(): # TODO: convert to json here
			if op == server_socket.SOCKET_OP.DATA_IN:
				self.process_lobby_message(client, msg)
			elif op == server_socket.SOCKET_OP.NEW_CONNECTION:
				self.handle_new_connection(client)
			elif op == server_socket.SOCKET_OP.HANGUP:
				del self.players[client]

			if self.game_state != GAME_STATE.LOBBY:
				break

	def game(self):
		self.new_game()
		while self.turn <= self.cards_dealt:
			#print('beginning of turn:', self.turn)
			self.broadcast_turn()
			self.play_turn()

		#print('game ended - returning to lobby')
		self.game_state = GAME_STATE.LOBBY
		self.broadcast_scoreboard()

	def process_game_message(self, client, bmsg):
		try:
			msg = json.loads(str(bmsg, 'utf-8').strip())
		except json.decoder.JSONDecodeError:
			print('json decode error...', bmsg)
			return MSG_TYPE.ERROR, None

		if msg.get('action') == 'give_card':
			return MSG_TYPE.GIVE_CARD, int(msg.get('card')) # TODO: safety

		return MSG_TYPE.ERROR, None

	def reject_new_connection(self, client):
		client.send(b'Game in progress! - auto disconnect')
		self.handle_disconnect(client)

	def play_turn(self):
		for client, msg, op in self.ssock.listen():
			if op == server_socket.SOCKET_OP.DATA_IN:
				msg_type, card = self.process_game_message(client, msg)
				if msg_type == MSG_TYPE.GIVE_CARD:
					if self.received_card(client, card):
						break
				else:
					print('command not implemented')
			elif op == server_socket.SOCKET_OP.NEW_CONNECTION:
				self.reject_new_connection(client)
			elif op == server_socket.SOCKET_OP.HANGUP:
				del self.players[client]
				print('Player', self.players[client], 'disconnected during game!!!') # TODO: how to handle this?

	def handle_new_connection(self, client):
		self.players[client] = Player(client, ''.join(random.sample(string.ascii_uppercase, 10)))

	def handle_disconnect(self, client):
		self.ssock.removeConnection(client)

	def new_game(self):
		self.board = board.Board()
		self.turn = 1
		self.broadcast_game_format()
		for player in self.players.values():
			player.set_hand(self.board.draw(self.cards_dealt))

	def received_card(self, client, card):
		if not self.players[client].set_card(card):
			send_json = json.dumps({'type': 'error', 'Not in your hand': str(card)})
			self.ssock.broadcast([client], send_json)  # TODO: broadcast?
			return False
		players_ready = [p for p in self.players.values() if p.card]
		if len(players_ready) != len(self.players):
			return False
		self.place_all_cards()
		return True

	def place_all_cards(self):
		players_ready = sorted(self.players.values(), key=lambda p: p.card)
		for p in players_ready:
			ret, penalty_for_5_card = self.board.place_card(p.card)
			if ret == board.CANDIDATE.TOO_LOW:
				selection = p.select_row(self.board.rows)
				penalty = self.board.reset_row(selection, p.card)
				p.score = p.score + penalty
				#msg = f'{p.name} takes {penalty} penalty, total: {p.score}\n' # TODO: json
				#self.ssock.broadcast(self.players.keys(), msg)
			elif ret == board.CANDIDATE.PENALTY:
				p.score += penalty_for_5_card
				#msg = f'{p.name} takes {penalty_for_5_card} penalty, total: {p.score}\n' # TODO json
				#self.ssock.broadcast(self.players.keys(), msg)
			elif ret == board.CANDIDATE.PUT:
				# print(p.name, p.card, 'ok')
				pass
			else:
				print('place_cards: unknown operation:', ret)
				exit(1)
		for p in players_ready:
			p.finish_card()
		#print('turn ending...')
		self.turn = self.turn + 1

	def broadcast_game_format(self):
		msg = {
			'type': 'info',
			'score_limit': self.score_limit,
			'nturns': self.cards_dealt,
			'nrows': len(self.board.rows),
			'row_len': self.board.max_row_len,
			'nplayers': len(self.players)}
		#self.ssock.broadcast(self.players.keys(), json.dumps(msg))
		pass

	def broadcast_turn(self):
		board = {'type': 'query', 'action': 'give_card', 'board': self.board.rows, 'turn': self.turn}
		for p in self.players.values():
			msg = board.copy()
			msg['hand'] = p.hand
			self.ssock.broadcast([p.socket], json.dumps(msg))

	def broadcast_scoreboard(self):
		msg = { 'type' : 'info' }
		scoreboard = [ { 'name': p.name, 'score': p.score, 'place': i + 1 } \
				for i, p in enumerate(sorted(self.players.values(), key=lambda p: p.score)) ]
		msg['scoreboard'] = scoreboard
		#self.ssock.broadcast(self.players.keys(), json.dumps(msg))
		print(json.dumps(msg, indent=4))
